package com.engine;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;

import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;

import static org.lwjgl.opengl.GL11.*;

public abstract class Engine {
	ArrayList<I3DRenderer> _3dRenderers = new ArrayList<I3DRenderer>();
	ArrayList<I2DRenderer> _2dRenderers = new ArrayList<I2DRenderer>();
	protected int width;
	protected  int height;
	long lastTime;
	RenderMode mode;
	/**
	 * Constructs engine.
	 * @param width width of render window
	 * @param height height of render window
	 * @param mode render mode, see {@link RenderMode}
	 */
	public Engine(int width, int height, RenderMode mode) {
		try {
			loadLib();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("Sorry");
			System.exit(0);
		}
		this.width = width;
		this.height = height;
		this.mode = mode;
		try {
			Display.setDisplayMode(new DisplayMode(width, height));
			Display.create();
			//Display.setVSyncEnabled(true);
			initialize();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}

	}
	/**
	 * Initialize engine.
	 */
	abstract public void initialize();
	/**
	 * Called before 3D scene rendering.
	 */
	abstract public void begin3D();
	/**
	 * Called before 2D overlay rendering.
	 */
	abstract public void begin2D();
	/**
	 * Adds I3DRenderer to engine.
	 * @param renderer I3DRenderer to add
	 */
	public void addI3DRenderer(I3DRenderer renderer)
	{
		_3dRenderers.add(renderer);
	}
	/**
	 * Adds I2DRenderer to engine.
	 * @param renderer I2DRenderer to add
	 */
	public void addI2DRenderer(I2DRenderer renderer)
	{
		_2dRenderers.add(renderer);
	}
	/**
	 * Begins engine rendering loop.
	 */
	public void enterLoop() {
		while (!Display.isCloseRequested()
				&& !Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
			int deltaTime = getDelta();
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			switch (mode) {
			case RENDER_2D: {
				begin2D();
				for (I2DRenderer render : _2dRenderers)
					render.render(deltaTime);
				break;
			}
			case RENDER_3D: {
				begin3D();
				for (I3DRenderer render : _3dRenderers)
					render.render(deltaTime);
				break;
			}
			case RENDER_MIXED: {
				begin3D();
				for (I3DRenderer render : _3dRenderers)
					render.render(deltaTime);
				begin2D();
				for (I2DRenderer render : _2dRenderers)
					render.render(deltaTime);
				break;
			}
			default:
				break;
			}
			Display.update();
			Display.sync(60);
		}
		Display.destroy();
		System.exit(0);
	}

	/**
	 * Returns delta time from last frame.
	 * 
	 * @return delta time, in milliseconds
	 */
	public int getDelta() {
		long time = getTime();
		int delta = (int) (time - lastTime);
		lastTime = time;

		return delta;
	}

	/**
	 * Returns current time.
	 * 
	 * @return current time in milliseconds
	 */
	public long getTime() {
		return (Sys.getTime() * 1000) / Sys.getTimerResolution();
	}

	/**
	 * Loads all LWJGL native libs.
	 * 
	 * @throws Exception
	 *             if lib load filed
	 */
	public static void loadLib() throws Exception {
		addLibraryPath("native/windows");
		if (System.getProperty("os.arch").equals("amd64")
				|| System.getProperty("os.arch").equals("x86_64"))
			System.loadLibrary("OpenAL64");
		else
			System.loadLibrary("OpenAL32");
	}

	/**
	 * Adds path to search libs.
	 * 
	 * @param s
	 *            path to search libs
	 * @throws Exception
	 */
	public static void addLibraryPath(String s) throws Exception {
		final Field usr_paths_field = ClassLoader.class
				.getDeclaredField("usr_paths");
		usr_paths_field.setAccessible(true);

		final String[] paths = (String[]) usr_paths_field.get(null);

		for (String path : paths) {
			if (path.equals(s))
				return;
		}

		final String[] new_paths = Arrays.copyOf(paths, paths.length + 1);
		new_paths[paths.length - 1] = s;
		usr_paths_field.set(null, new_paths);
	}
}
